#include <common.h>
#include <dm.h>
#include <clk.h>
#include <clk-uclass.h>
#include <asm/io.h>

#include <mach/loongson.h>

DECLARE_GLOBAL_DATA_PTR;

static void calc_clocks(void)
{
	u32 ls2x_refclk = OSC_CLK / 100; //参考时钟固定为100MHz
	u64 ctrl = 0;
//	u32 dc_clk;
	u32 gmac_clk;
//	unsigned int l1div_out;
	unsigned int l1div_loopc, l1div_ref;
	unsigned int l2div_out;
	unsigned int mult, div;

	/* node cpu clk */
	ctrl = (u64)readl(LS2X_NODE_PLL_L);
	ctrl = ctrl | ((u64)readl(LS2X_NODE_PLL_L + 4) << 32);
//	l1div_out = (ctrl >> NODE_L1DIV_OUT_SHIFT) & NODE_L1DIV_OUT_MARK;
	l1div_loopc = (ctrl >> NODE_L1DIV_LOOPC_SHIFT) & NODE_L1DIV_LOOPC_MARK;
	l1div_ref = (ctrl >> NODE_L1DIV_REF_SHIFT) & NODE_L1DIV_REF_MARK;
	ctrl = (u64)readl(LS2X_NODE_PLL_H);
	ctrl = ctrl | ((u64)readl(LS2X_NODE_PLL_H + 4) << 32);
	l2div_out = (ctrl >> NODE_L2DIV_OUT_SHIFT) & NODE_L2DIV_OUT_MARK;
	mult = l1div_loopc;
	div = l1div_ref * l2div_out;
	gd->cpu_clk = (unsigned long)((ls2x_refclk * mult / div) * 100);

	/* ddr gpu hda clk */
	ctrl = (u64)readl(LS2X_DDR_PLL_L);
	ctrl = ctrl | ((u64)readl(LS2X_DDR_PLL_L + 4) << 32);
//	l1div_out = (ctrl >> DDR_L1DIV_OUT_SHIFT) & DDR_L1DIV_OUT_MARK;
	l1div_loopc = (ctrl >> DDR_L1DIV_LOOPC_SHIFT) & DDR_L1DIV_LOOPC_MARK;
	l1div_ref = (ctrl >> DDR_L1DIV_REF_SHIFT) & DDR_L1DIV_REF_MARK;
	ctrl = (u64)readl(LS2X_DDR_PLL_H);
	ctrl = ctrl | ((u64)readl(LS2X_DDR_PLL_H + 4) << 32);
	l2div_out = (ctrl >> DDR_L2DIV_OUT_DDR_SHIFT) & DDR_L2DIV_OUT_DDR_MARK;
	mult = l1div_loopc;
	div = l1div_ref * l2div_out;
	gd->mem_clk = (unsigned long)((ls2x_refclk * mult / div) * 100);

	/* dc gmac clk */
	ctrl = (u64)readl(LS2X_DC_PLL_L);
	ctrl = ctrl | ((u64)readl(LS2X_DC_PLL_L + 4) << 32);
//	l1div_out = (ctrl >> DC_L1DIV_OUT_SHIFT) & DC_L1DIV_OUT_MARK;
	l1div_loopc = (ctrl >> DC_L1DIV_LOOPC_SHIFT) & DC_L1DIV_LOOPC_MARK;
	l1div_ref = (ctrl >> DC_L1DIV_REF_SHIFT) & DC_L1DIV_REF_MARK;
	ctrl = (u64)readl(LS2X_DC_PLL_H);
	ctrl = ctrl | ((u64)readl(LS2X_DC_PLL_H + 4) << 32);
	l2div_out = (ctrl >> DC_L2DIV_OUT_DC_SHIFT) & DC_L2DIV_OUT_DC_MARK;
	mult = l1div_loopc;
	div = l1div_ref * l2div_out;
//	dc_clk = ls2x_refclk * mult / div;

	l2div_out = (ctrl >> DC_L2DIV_OUT_GMAC_SHIFT) & DC_L2DIV_OUT_GMAC_MARK;
	div = l1div_ref * l2div_out;
	gmac_clk = ls2x_refclk * mult / div;

	/* apb usb sata clk */
	ctrl = (u64)readl(LS2X_FREQ_SCALE);
	ctrl = ctrl | ((u64)readl(LS2X_FREQ_SCALE + 4) << 32);
	{
	unsigned int apb_freqscale;
	apb_freqscale = (ctrl >> FREQSCALE_APB_SHIFT) & FREQSCALE_APB_MARK;
	gd->bus_clk = (unsigned long)((gmac_clk * (apb_freqscale + 1) / 8) * 100);
	}

	gd->arch.pll_clk = (unsigned long)(ls2x_refclk * 100);
}

/* arch specific CPU init after DM */
int arch_cpu_init_dm(void)
{
	int ret;
	struct udevice *dev;

	ret = uclass_get_device(UCLASS_CLK, 0, &dev);
	if (ret) {
		printf("clk-uclass not found\n");
		return 0;
	}

	return 0;
}

int mach_cpu_init(void)
{
	calc_clocks();

	return 0;
}

int dram_init(void)
{
#if 0
	gd->ram_size = get_ram_size ((long *)CONFIG_SYS_SDRAM_BASE, CONFIG_SYS_SDRAM_SIZE);
#else
	if (fdtdec_setup_mem_size_base() != 0)
		return -EINVAL;
#endif
	return 0;
}

int dram_init_banksize(void)
{
	fdtdec_setup_memory_banksize();

	return 0;
}

const char *get_core_name(void)
{
	u32 proc_id;
	const char *str;

	proc_id = read_cpucfg(LOONGARCH_CPUCFG0) & 0xffff;
	switch (proc_id) {
	case 0x0000a000:
		str = "LA264";
		break;
	default:
		str = "Unknown";
	}

	return str;
}

int print_cpuinfo(void)
{
	printf("Core: %s\n", get_core_name());
	printf("Speed: Cpu @ %ld MHz/ Mem @ %ld MHz/ Bus @ %ld MHz\n",
			gd->cpu_clk/1000000, gd->mem_clk/1000000, gd->bus_clk/1000000);
	return 0;
}
